
/**
 * LICENCIA LGPL:
 * 
 * Esta librería es Software Libre; Usted puede redistribuirla y/o modificarla
 * bajo los términos de la GNU Lesser General Public License (LGPL) tal y como 
 * ha sido publicada por la Free Software Foundation; o bien la versión 2.1 de 
 * la Licencia, o (a su elección) cualquier versión posterior.
 * 
 * Esta librería se distribuye con la esperanza de que sea útil, pero SIN 
 * NINGUNA GARANTÍA; tampoco las implícitas garantías de MERCANTILIDAD o 
 * ADECUACIÓN A UN PROPÓSITO PARTICULAR. Consulte la GNU Lesser General Public 
 * License (LGPL) para más detalles
 * 
 * Usted debe recibir una copia de la GNU Lesser General Public License (LGPL) 
 * junto con esta librería; si no es así, escriba a la Free Software Foundation 
 * Inc. 51 Franklin Street, 5º Piso, Boston, MA 02110-1301, USA o consulte
 * <http://www.gnu.org/licenses/>.
 *
 * Copyright 2011 Agencia de Tecnología y Certificación Electrónica
 */
package es.accv.arangi.base.device;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;

import org.apache.log4j.Logger;

import sun.security.pkcs11.SunPKCS11;

import com.sun.security.auth.callback.DialogCallbackHandler;

import es.accv.arangi.base.device.util.CustomDialogCallbackHandler;
import es.accv.arangi.base.device.util.MozillaPkcs11Manager;
import es.accv.arangi.base.exception.device.InitializeProviderException;
import es.accv.arangi.base.exception.device.LoadingObjectException;
import es.accv.arangi.base.exception.device.MozillaKeyStoreException;
import es.accv.arangi.base.exception.device.MozillaNotFoundException;
import es.accv.arangi.base.exception.device.OpeningDeviceException;
import es.accv.arangi.base.util.Util;

/**
 * Clase para tratar almacenes de claves de Mozilla Firefox.<br><br>
 * 
 * Este almacén es el propio del navegador Firefox. Si se desea, la clase puede tratar 
 * los certificados que se encuentran en dispositivos PKCS#11 y que han sido configurados 
 * como dispositivos de seguridad en Firefox. Esta solución es una buena idea en sistemas 
 * Linux donde no se dispone del almacén de Windows para gestionar los certificados en 
 * tarjeta.<br><br>
 * 
 * NOTA: Esta clase no es capaz de trabajar con Firefox en Mac OS X por problemas con las 
 * dependencias del módulo PKCS#11.<br><br>
 * 
 * Ejemplo de uso:<br><br>
 * 
 * <code>
 *  IDocument document = new FileDocument(new File ("/documento.txt"));<br>
 * 	MozillaKeyStoreManager manager = new MozillaKeyStoreManager ("Escriba contraseña maestra Firefox : ", "Contraseña maestra Firefox", false);<br>
 * 	String aliases = manager.getAliasNamesList();<br>
 * 	for (int i=0;i<aliases.length;i++) {<br>
 *  &nbsp;&nbsp;System.out.println ("Certificate: " + manager.getCertificate(aliases[i]));<br>
 * 	&nbsp;&nbsp;System.out.println ("Firma: " + manager.signDocument(document, aliases[i])); // Firma con la clave privada del alias<br>
 * 	}<br>
 * </code><br>
 * 
 * @author <a href="mailto:jgutierrez@accv.es">José M Gutiérrez</a>
 */
public class MozillaKeyStoreManager extends AbstractBrowserKeyStoreManager {

	/*
	 * Logger de la clase
	 */
	Logger logger = Logger.getLogger(MozillaKeyStoreManager.class);
	
	/*
	 * Nombre del provider
	 */
	String PROVIDER_NAME = "NSS";
	
	/*
	 * Array de almacenes PKCS#11 que utiliza Mozilla 
	 */
	List<MozillaPkcs11Manager> pkcs11Managers;
	
	/*
	 * Texto del dialogo en el que se pedirá el pin al usuario
	 */
	String textPinDialog = null;
	
	/*
	 * Texto del título del dialogo en el que se pedirá el pin al usuario
	 */
	String titleDialog = null;
	
	/**
	 * Constructor: inicializa el proveedor de SUN si es necesario para acceder al 
	 * repositorio de Mozilla.<br><br>
	 * 
	 * Se cargarán los módulos PKCS#11 asociados al almacén de claves de Mozilla.
	 * 
	 * @throws MozillaNotFoundException No se ha podido encontrar la carpeta de
	 * 	instalación de Mozilla en los archivos de programa o no se ha encontrado
	 * 	la carpeta del perfil de Mozilla del usuario. 
	 * @throws MozillaKeyStoreException No se puede abrir el almacén de claves del
	 * 	usuario de Mozilla.
	 */
	public MozillaKeyStoreManager () throws MozillaNotFoundException, MozillaKeyStoreException {
		
		initKeystores(new DialogCallbackHandler(), false);

	}
	
	/**
	 * Constructor: inicializa el proveedor de SUN si es necesario para acceder al 
	 * repositorio de Mozilla.<br><br>
	 * 
	 * Se cargarán los módulos PKCS#11 asociados al almacén de claves de Mozilla.
	 * 
	 * @param pinDialogText texto del dialogo en el que se pedirá el pin al usuario.
	 * @param titleDialog texto del título del dialogo en el que se pedirá el pin al usuario.
	 * 
	 * @throws MozillaNotFoundException No se ha podido encontrar la carpeta de
	 * 	instalación de Mozilla en los archivos de programa o no se ha encontrado
	 * 	la carpeta del perfil de Mozilla del usuario. 
	 * @throws MozillaKeyStoreException No se puede abrir el almacén de claves del
	 * 	usuario de Mozilla.
	 */
	public MozillaKeyStoreManager (String textPinDialog, String titleDialog) throws MozillaNotFoundException, MozillaKeyStoreException {
		
		this.textPinDialog = textPinDialog;
		this.titleDialog = titleDialog;
		
		initKeystores(new CustomDialogCallbackHandler(textPinDialog, titleDialog), false);

	}
	
	/**
	 * Constructor: inicializa el proveedor de SUN si es necesario para
	 * acceder al repositorio de Mozilla.
	 * 
	 * @param withoutPkcs11Modules Si es cierto no se cargarán los módulos PKCS#11 
	 * 	asociados al almacén de claves de Mozilla.
	 * @throws MozillaNotFoundException No se ha podido encontrar la carpeta de
	 * 	instalación de Mozilla en los archivos de programa o no se ha encontrado
	 * 	la carpeta del perfil de Mozilla del usuario. 
	 * @throws MozillaKeyStoreException No se puede abrir el almacén de claves del
	 * 	usuario de Mozilla.
	 */
	public MozillaKeyStoreManager (boolean withoutPkcs11Modules) throws MozillaNotFoundException, MozillaKeyStoreException {
		
		initKeystores(new DialogCallbackHandler(), withoutPkcs11Modules);

	}
	
	/**
	 * Constructor: inicializa el proveedor de SUN si es necesario para
	 * acceder al repositorio de Mozilla.
	 * 
	 * @param pinDialogText texto del dialogo en el que se pedirá el pin al usuario.
	 * @param titleDialog texto del título del dialogo en el que se pedirá el pin al usuario.
	 * @param withoutPkcs11Modules Si es cierto no se cargarán los módulos PKCS#11 
	 * 	asociados al almacén de claves de Mozilla.
	 * 
	 * @throws MozillaNotFoundException No se ha podido encontrar la carpeta de
	 * 	instalación de Mozilla en los archivos de programa o no se ha encontrado
	 * 	la carpeta del perfil de Mozilla del usuario. 
	 * @throws MozillaKeyStoreException No se puede abrir el almacén de claves del
	 * 	usuario de Mozilla.
	 */
	public MozillaKeyStoreManager (String textPinDialog, String titleDialog, boolean withoutPkcs11Modules) throws MozillaNotFoundException, MozillaKeyStoreException {
		
		this.textPinDialog = textPinDialog;
		this.titleDialog = titleDialog;
		
		initKeystores(new CustomDialogCallbackHandler(textPinDialog, titleDialog), withoutPkcs11Modules);

	}
	
	//-- Métodos públicos
	
	/**
	 * Cerrar el keystore y eliminar el provider de SUN de la lista de proveedores
	 */
	public void close() {
		logger.debug ("Cerrando Keystore...");

		ks = null;
		
		//-- Eliminar provider de SUN
		logger.debug("[CLOSE]::Eliminando el provider de SUN");
		Security.removeProvider("SunPKCS11-NSS");
		logger.debug("[CLOSE]::Eliminado el provider de SUN");
		
		logger.debug("Cerrado Keystore...OK");
		
		//-- Cerrar PKCS11s
		for (Iterator<MozillaPkcs11Manager> iterator = pkcs11Managers.iterator(); iterator.hasNext();) {
			MozillaPkcs11Manager pkcs11Manager = iterator.next();
			logger.debug("[MozillaKeyStoreManager.getAliasNamesList]::Cerrando '" + pkcs11Manager.getProviderName()  + "'");
			pkcs11Manager.close();
		}
		
	}
	
	@Override
	public void initialize() {
		if (textPinDialog != null || titleDialog != null) {
			try {
				initKeystores(new DialogCallbackHandler(), false);
			} catch (MozillaKeyStoreException e) {
				logger.info("[MozillaKeyStoreManager.initialize]:: Error al reinicializar el KeyStore", e);
			} catch (MozillaNotFoundException e) {
				logger.info("[MozillaKeyStoreManager.initialize]:: Error al reinicializar el KeyStore", e);
			}
		} else {
			try {
				initKeystores(new CustomDialogCallbackHandler(textPinDialog, titleDialog), false);
			} catch (MozillaKeyStoreException e) {
				logger.info("[MozillaKeyStoreManager.initialize]:: Error al reinicializar el KeyStore", e);
			} catch (MozillaNotFoundException e) {
				logger.info("[MozillaKeyStoreManager.initialize]:: Error al reinicializar el KeyStore", e);
			}
		}
		
	}
	
	/*
	 * (non-Javadoc)
	 * @see es.accv.arangi.base.device.DeviceManager#getAliasNamesList()
	 */
	public String[] getAliasNamesList() {
		logger.debug("Obteniendo todos los alias...");
		
		//-- Obtener los alias del keystore
		Enumeration enumAlias = null;
		try {
			enumAlias = ks.aliases();
		} catch (KeyStoreException e) {
			//-- Este error sólo se produce si no se ha inicializado la clase, lo que
			//-- ya se comprueba y no puede ocurrir
			logger.debug("[MozillaKeyStoreManager.getAliasNamesList]::Keystore no inicializado", e);
		}
		List lAlias = new ArrayList ();
		while (enumAlias != null && enumAlias.hasMoreElements()) {
			String alias = (String) enumAlias.nextElement();
			lAlias.add(alias);
		}
		
		//-- Obtener los alias de los PKCS#11
		for (Iterator<MozillaPkcs11Manager> iterator = pkcs11Managers.iterator(); iterator.hasNext();) {
			MozillaPkcs11Manager pkcs11Manager = iterator.next();
			
			//-- Obtener los alias
			logger.debug("[MozillaKeyStoreManager.getAliasNamesList]::Obteniendo alias para '" + pkcs11Manager.getProviderName()  + "'");
			lAlias.addAll(pkcs11Manager.getAliasNamesList());
		}
		
		return (String[]) lAlias.toArray(new String[0]);
	}
	
	/*
	 * (non-Javadoc)
	 * @see es.accv.arangi.base.device.DeviceManager#getCertificate(java.lang.String)
	 */
	public X509Certificate getCertificate(String alias) {
		logger.debug("getCertificate...");
		logger.debug("alias: " + alias);
		
		//-- Buscar en el keystore
		try {
			X509Certificate certificate = (X509Certificate) ks.getCertificate(alias);
			logger.debug ("[MozillaKeyStoreManager.getCertificate]::Obtenido certificado::" + certificate);
			if (certificate != null) {
				return certificate;
			}
		} catch (KeyStoreException e) {
			//-- Este error sólo se produce si no se ha inicializado la clase, lo que
			//-- ya se comprueba y no puede ocurrir
			logger.debug("[MozillaKeyStoreManager.getCertificate]::Keystore no inicializado", e);
		}
		
		//-- Buscar en los alias de los PKCS#11
		X509Certificate certificate;
		for (Iterator<MozillaPkcs11Manager> iterator = pkcs11Managers.iterator(); iterator.hasNext();) {
			MozillaPkcs11Manager pkcs11Manager = iterator.next();
			
			//-- Buscar
			logger.debug("[MozillaKeyStoreManager.getCertificate]::Buscando en los alias para '" + pkcs11Manager.getProviderName()  + "'");
			if ((certificate = pkcs11Manager.getCertificate(alias)) != null) {
				logger.debug("[MozillaKeyStoreManager.getCertificate]::Encontrado alias '" + alias  + "' en '" + pkcs11Manager.getProviderName()  + "'");
				return certificate;
			}
		}
		
		logger.info("[MozillaKeyStoreManager.getCertificate]::No se ha encontrado el alias '" + alias + "'");
		return null;		
	}

	/*
	 * (non-Javadoc)
	 * @see es.accv.arangi.base.device.DeviceManager#getPrivateKey(java.lang.String, java.lang.String)
	 */
	public PrivateKey getPrivateKey(String alias, String keyPassword) throws LoadingObjectException{
		
		logger.debug("[MozillaKeyStoreManager.getPrivateKey]::Entrada::" + Arrays.asList(new Object [] { alias }));
		
		//-- Buscar en el keystore
		try {
			//-- Buscar por alias y viendo que sea una clave
			if (!isAliasFree(alias) && ks.isKeyEntry(alias)) {
				logger.debug("[MozillaKeyStoreManager.getPrivateKey]::Encontrada clave privada con alias '" + alias + "'");
				return (PrivateKey) ks.getKey(alias, keyPassword.toCharArray());
			}
		} catch (KeyStoreException e) {
			//-- Este error sólo se produce si no se ha inicializado la clase, lo que
			//-- ya se comprueba y no puede ocurrir
			logger.debug("[MozillaKeyStoreManager.getPrivateKey]::Keystore no inicializado", e);
		} catch (NoSuchAlgorithmException e) {
			//-- Este error sólo se produce si no se ha cargado Bouncy, con lo que
			//-- el error se habría producido mucho antes
			logger.debug("[MozillaKeyStoreManager.getPrivateKey]::No se encuentra el algoritmo para alguno de los certificados de la cadena", e);
			return null;
		} catch (UnrecoverableKeyException e) {
			logger.info("[MozillaKeyStoreManager.getPrivateKey]::La clave privada del alias '" + alias + "' no ha podido ser recuperada. " +
					"Compruebe que su password coincide con el PIN del dispositivo.", e);
			throw new LoadingObjectException("La clave privada del alias '" + alias + "' no ha podido ser recuperada. Compruebe " +
					"que su password coincide con el PIN del dispositivo.", e);
		} 

		//-- Buscar en los alias de los PKCS#11
		PrivateKey pk;
		for (Iterator<MozillaPkcs11Manager> iterator = pkcs11Managers.iterator(); iterator.hasNext();) {
			MozillaPkcs11Manager pkcs11Manager = iterator.next();
			
			//-- Buscar
			logger.debug("[MozillaKeyStoreManager.getPrivateKey]::Buscando en los alias para '" + pkcs11Manager.getProviderName()  + "'");
			if ((pk = pkcs11Manager.getPrivateKey(alias)) != null) {
				logger.debug("[MozillaKeyStoreManager.getPrivateKey]::Encontrado alias '" + alias  + "' en '" + pkcs11Manager.getProviderName()  + "'");
				return pk;
			}
		}
		
		logger.info("[MozillaKeyStoreManager.getPrivateKey]::No se ha encontrado una pk en el alias '" + alias + "'");
		return null;		
	}

	/* (non-Javadoc)
	 * @see es.accv.arangi.base.device.DeviceManager#isAliasFree(java.lang.String)
	 */
	public boolean isAliasFree(String alias){
		
		logger.debug ("[MozillaKeyStoreManager.isAliasFree]::Entrada::" + alias);
		
		boolean isFree = false;
		try {
			isFree = !ks.containsAlias(alias);
		} catch (KeyStoreException e) {
			logger.info("[MozillaKeyStoreManager.isAliasFree]::Error comprobando si existe el alias '" + alias + "'", e);
		}
		
		//-- Buscar en los alias de los PKCS#11
		for (Iterator<MozillaPkcs11Manager> iterator = pkcs11Managers.iterator(); iterator.hasNext();) {
			MozillaPkcs11Manager pkcs11Manager = iterator.next();
			
			//-- Buscar
			logger.debug("[MozillaKeyStoreManager.isAliasFree]::Buscando en los alias para '" + pkcs11Manager.getProviderName()  + "'");
			if (pkcs11Manager.getAliasNamesList().contains(alias)) {
				logger.debug("[MozillaKeyStoreManager.isAliasFree]::Encontrado alias '" + alias  + "' en '" + pkcs11Manager.getProviderName()  + "'");
				return true;
			}
		}

		return isFree;
	}
	
	//-- Métodos privados
	
	/*
	 * Inicializa el keystore de Mozilla y los keystores pkcs11 asociados
	 */
	private void initKeystores(CallbackHandler handler, boolean withoutPkcs11Modules) throws MozillaKeyStoreException, MozillaNotFoundException{
		
		List<KeyStore> lKeystores = new ArrayList<KeyStore>();
		this.pin = "";
		this.ksType = STORE_TYPE_MOZILLA;
		
		SunPKCS11 pk11provider;
		if (!Util.existsCryptographicProvider(SUN_PROVIDER_PREFIX + PROVIDER_NAME)) {
			
			//-- Buscar la carpeta de instalación de Mozilla
			File mozillaFolder = getMozillaFolder ();
			
			//-- Buscar la carpeta para el secmod
			File secModFolder = getSecModFolder ();
			
			//-- Cargar las librerías de Mozilla
			try {
				if (System.getProperty("os.name").startsWith("Mac OS X")) {
					configureMacNSS(mozillaFolder);
				} else {
					loadMozillaLibraries (mozillaFolder);
				}
			} catch (Throwable e) {
				logger.info("[MozillaKeyStoreManager.initKeystores]::No se han podido cargar las librerías para Mozilla Firefox", e);
				throw new MozillaKeyStoreException("No se han podido cargar las librerías para Mozilla Firefox", e);
			}
			
			try {
				//-- Configurar el provider
				String providerConfig = "name = " + PROVIDER_NAME + "\r" + 
						   "library = " + new File (mozillaFolder, getMozillaNSSLibraryName()).getCanonicalPath() + "\r" + 
						   "attributes= compatibility" + "\r" +
						   "slot=2\r" + 
						   "nssArgs=\"" + 
						   "configdir='" + secModFolder.getCanonicalPath() + "' " +
						   "certPrefix='' " + 
						   "keyPrefix='' " + 
						   "secmod=' secmod.db' " + 
						   "flags=readWrite\"\r";

				providerConfig = providerConfig.replaceAll("\\\\", "/");
		
				//-- Inicializar el provider
				ByteArrayInputStream localByteArrayInputStream = new ByteArrayInputStream(providerConfig.getBytes());
				pk11provider = new SunPKCS11(localByteArrayInputStream);
				Security.addProvider(pk11provider);
			} catch (Throwable e) {
				logger.info("[MozillaKeyStoreManager.initKeystores]::No se ha podido inicializar el proveedor criptográfico PKCS#11 para Mozilla Firefox", e);
				throw new MozillaKeyStoreException("No se ha podido inicializar el proveedor criptográfico PKCS#11 para Mozilla Firefox", e);
			}
		} else {
			pk11provider = (SunPKCS11) Security.getProvider(SUN_PROVIDER_PREFIX + PROVIDER_NAME);
		}
		
		try {
			pk11provider.login(new Subject(), handler);
			ks = KeyStore.getInstance("PKCS11",pk11provider);
			ks.load(null, null);
			lKeystores.add(ks);
		} catch (Exception e) {
			logger.info("[MozillaKeyStoreManager.initKeystores]::No se ha podido abrir el almacén de claves del usuario de Mozilla Firefox", e);
			throw new MozillaKeyStoreException("No se ha podido abrir el almacén de claves del usuario de Mozilla Firefox", e);
		}
		
		//-- Tarjetas tratadas con Mozilla
		pkcs11Managers = new ArrayList<MozillaPkcs11Manager>();
		if (withoutPkcs11Modules) {
			//-- No se usan los PKCS#11 asociados a Mozilla
			logger.debug("[MozillaKeyStoreManager.initKeystores]::No se usan los PKCS#11 asociados a Mozilla");
		} else {
			logger.debug("[MozillaKeyStoreManager.initKeystores]::Se usan los PKCS#11 asociados a Mozilla");
			Hashtable<String, File> hmPkcs11Modules = getMozillaPKCS11Modules();
			Enumeration<String> enumeration = hmPkcs11Modules.keys();
			while (enumeration.hasMoreElements()) {
				String name = enumeration.nextElement();
				try {
					MozillaPkcs11Manager pkcs11Manager = new MozillaPkcs11Manager(name, hmPkcs11Modules.get(name));
					pkcs11Managers.add(pkcs11Manager);
				} catch(InitializeProviderException e) {
					logger.info("[MozillaKeyStoreManager.initKeystores]::No se ha podido cargar el proveedor PKCS#11 para el módulo '" + name + "'", e);
					continue;
				} catch(OpeningDeviceException e) {
					logger.info("[MozillaKeyStoreManager.initKeystores]::No se ha podido abrir el almacén de claves del módulo '" + name + "'", e);
					continue;
				} catch (Throwable e) {
					logger.info("[MozillaKeyStoreManager.initKeystores]::Error desconocido al cargar el proveedor PKCS#11 para el módulo '" + name + "'", e);
					continue;
				}
				logger.debug("[MozillaKeyStoreManager.initKeystores]::Se ha inicializado el módulo '" + name + "'");
			}
		}
	}

	//-- Métodos privados
	
	/**
	 * Obtiene la ruta a la carpeta donde se encuentra instalado Mozilla
	 * 
	 * @return Ruta a la carpeta donde se encuentra instalado Mozilla
	 * @throws MozillaNotFoundException No se ha encontrado una instalación de Mozilla
	 */
	private File getMozillaFolder() throws MozillaNotFoundException {

		//-- Si el SO es Windows ...
		if (System.getProperty("os.name").contains("indows")) {
			logger.debug("[MozillaKeyStoreManager]::Buscando la carpeta de instalación de Mozilla en Windows");
			File mozillaFolder = new File (new File (System.getenv("PROGRAMFILES")), "Mozilla Firefox");
			if (mozillaFolder.exists()) {	
				logger.debug("[MozillaKeyStoreManager]::Se ha encontrado la carpeta de instalación de Mozilla Firefox en Windows: " + mozillaFolder.getAbsolutePath());
				
				//-- Si el path al directorio contiene caracteres extraños no funcionará la carga del
				//-- provider PKCS#11 de SUN
				if (mozillaFolder.getAbsolutePath().contains(")") || 
						mozillaFolder.getAbsolutePath().contains("(") || 
						mozillaFolder.getAbsolutePath().contains("\u007E")) { 
					
					//-- Copiar las DLLs a una ubicación temporal que será la que utilizará a partir de ahora
					try {
						logger.debug("[MozillaKeyStoreManager]::La carpeta de instalación de Mozilla Firefox en Windows contiene caracteres incompatibles con provider PKCS11: " + mozillaFolder.getAbsolutePath());
						File mozillaTempFolder = File.createTempFile("mozillaTemp", null);
						mozillaTempFolder.delete();
						mozillaTempFolder.mkdirs();
						Util.copyFile(new File (mozillaFolder, "softokn3.dll"), new File (mozillaTempFolder, "softokn3.dll"));
						Util.copyFile(new File (mozillaFolder, "plc4.dll"), new File (mozillaTempFolder, "plc4.dll"));
						Util.copyFile(new File (mozillaFolder, "plds4.dll"), new File (mozillaTempFolder, "plds4.dll"));
						Util.copyFile(new File (mozillaFolder, "nspr4.dll"), new File (mozillaTempFolder, "nspr4.dll"));
						
						try { 
							Util.copyFile(new File (mozillaFolder, "mozutils.dll"), new File (mozillaTempFolder, "mozutils.dll"));
						} catch (FileNotFoundException e) {}
						try { 
							Util.copyFile(new File (mozillaFolder, "mozglue.dll"), new File (mozillaTempFolder, "mozglue.dll"));
						} catch (FileNotFoundException e) {}
						try { 
							Util.copyFile(new File (mozillaFolder, "mozsqlite3.dll"), new File (mozillaTempFolder, "mozsqlite3.dll"));
						} catch (FileNotFoundException e) {}
						try { 
							Util.copyFile(new File (mozillaFolder, "sqlite3.dll"), new File (mozillaTempFolder, "sqlite3.dll"));
						} catch (FileNotFoundException e) {}
						try { 
							Util.copyFile(new File (mozillaFolder, "mozcrt19.dll"), new File (mozillaTempFolder, "mozcrt19.dll"));
						} catch (FileNotFoundException e) {}
						try { 
							Util.copyFile(new File (mozillaFolder, "nssutil3.dll"), new File (mozillaTempFolder, "nssutil3.dll"));
						} catch (FileNotFoundException e) {}
						try { 
							Util.copyFile(new File (mozillaFolder, "freebl3.dll"), new File (mozillaTempFolder, "freebl3.dll"));
						} catch (FileNotFoundException e) {}
						try { 
							Util.copyFile(new File (mozillaFolder, "nssdbm3.dll"), new File (mozillaTempFolder, "nssdbm3.dll"));
						} catch (FileNotFoundException e) {}
						
						return mozillaTempFolder;
					} catch (Exception e) {
						logger.info("[MozillaKeyStoreManager]::No se han podido cargar las DLLs de Mozilla Firefoxen una carpeta temporal", e);
						throw new MozillaNotFoundException("No se han podido cargar las DLLs de Mozilla Firefoxen una carpeta temporal", e);
					}
				}
				
				return mozillaFolder;
			}
		}
		
		//-- Si el SO es Linux / Unix ...
		if(System.getProperty("os.name").contains("inux") || System.getProperty("os.name").contains("SunOS") || 
				System.getProperty("os.name").contains("olaris")){
			logger.debug("[MozillaKeyStoreManager]::Buscando la carpeta con las librerías de Mozilla en Linux/Unix");
			String[] posiblesCarpetas = new String[] {
                    "/usr/lib/firefox-" + searchLastFirefoxVersion("/usr/lib/"),
                    "/usr/lib/firefox",
                    "/opt/firefox",
                    "/opt/firefox-" + searchLastFirefoxVersion("/opt/"),
                    "/lib",
                    "/usr/lib",
                    "/usr/lib/nss",
                    "/opt/fedora-ds/clients/lib",
                    "/usr/lib/i386-linux-gnu/nss"
			};
			
			for(String carpeta : posiblesCarpetas) {
				if (new File(new File (carpeta), "libsoftokn3.so").exists()) {
					logger.debug("[MozillaKeyStoreManager]::Se ha encontrado la carpeta con las librerías de Mozilla Firefox en Linux/Unix: " + carpeta);
					return new File(carpeta);
				}
			}
		}
		
		//-- Si el SO es Mac OS X
		if(System.getProperty("os.name").startsWith("Mac OS X")) {
			logger.debug("[MozillaKeyStoreManager]::Buscando la carpeta con las librerías de Mozilla en Mac OS X");
			String[] posiblesCarpetas = new String[] {
                    "/Applications/Firefox.app/Contents/MacOS",
                    "/lib",
                    "/usr/lib",
                    "/usr/lib/nss",
                    "/Applications/Minefield.app/Contents/MacOS"
			};
			
			for(String carpeta : posiblesCarpetas) {
				if (new File(new File (carpeta), "libsoftokn3.dylib").exists()) {
					logger.debug("[MozillaKeyStoreManager]::Se ha encontrado la carpeta con las librerías de Mozilla Firefox en Mac OS X: " + carpeta);
					return new File(carpeta);
				}
			}
		}
		
		logger.info("[MozillaKeyStoreManager]::No se ha podido obtener la carpeta de instalación de Mozilla Firefox");
		throw new MozillaNotFoundException("No se ha podido obtener la carpeta de instalación de Mozilla Firefox");
	}
	
	/**
	 * Obtiene la ruta donde se encuentran los ficheros con el almacén de claves de Mozilla
	 * 
	 * @return Ruta donde se encuentran los ficheros con el almacén de claves de Mozilla
	 * @throws MozillaNotFoundException No se ha encontrado una instalación de Mozilla
	 */
	private File getSecModFolder() throws MozillaNotFoundException {
		
		if (System.getProperty("os.name").contains("indows")) {
			logger.debug("[MozillaKeyStoreManager]::Buscando el almacén de claves de Mozilla en Windows");
			
			//-- Buscar carpeta de mozilla en Datos de Programa del usuario
			File secModFolder = new File (new File (System.getenv("APPDATA")), "Mozilla/Firefox");
			if (!secModFolder.exists() || secModFolder.list() == null || secModFolder.list().length == 0) {
				logger.info("[MozillaKeyStoreManager]::No se ha podido obtener la carpeta con el perfil del usuario de Mozilla Firefox: " + secModFolder.getAbsolutePath());
				throw new MozillaNotFoundException("No se ha podido obtener la carpeta con el perfil del usuario de Mozilla Firefox: " + secModFolder.getAbsolutePath());
			}
			logger.debug("[MozillaKeyStoreManager]::La carpeta de usuario de Mozilla Firefox es: " + secModFolder.getAbsolutePath());
			
			//-- Obtener el fichero de inicio que contiene la referencia de los perfiles
			File iniFile = new File (secModFolder, "profiles.ini");
			if (!iniFile.exists() || !iniFile.isFile()) {
				logger.info("[MozillaKeyStoreManager]::No se ha podido obtener el fichero de inicio con los perfiles de Mozilla: " + iniFile.getAbsolutePath());
				throw new MozillaNotFoundException("No se ha podido obtener el fichero de inicio con los perfiles de Mozilla: " + iniFile.getAbsolutePath());
			}
			
			//-- Obtener la carpeta del perfil
			try {
				return getFirefoxProfileDirectory (iniFile);
			} catch (IOException e) {
				logger.info("[MozillaKeyStoreManager]::No se ha podido obtener la carpeta con el perfil del usuario en Windows", e);
				throw new MozillaNotFoundException("No se ha podido obtener la carpeta con el perfil del usuario en Windows", e);
			}
		}
		
		//-- Si estamos en un Linux...
		logger.debug("[MozillaKeyStoreManager]::Buscando el almacén de claves de Mozilla en Linux");
		File regFile = new File(new File (System.getProperty("user.home")), "/.mozilla/firefox/profiles.ini");
		if (regFile.exists()) {
			try {
				return getFirefoxProfileDirectory(regFile);
			} catch (Exception e) {
				logger.info("[MozillaKeyStoreManager]::No se ha podido obtener la carpeta con el perfil del usuario en Linux", e);
				throw new MozillaNotFoundException("No se ha podido obtener la carpeta con el perfil del usuario en Linux", e);
			}
		}
		
		//-- Si es un Mac OS X...
		regFile = new File(new File (System.getProperty("user.home")), "/Library/Application Support/Firefox/profiles.ini");
		if (regFile.exists()) {
			try {
				return getFirefoxProfileDirectory(regFile);
			} catch (Exception e) {
				logger.info("[MozillaKeyStoreManager]::No se ha podido obtener la carpeta con el perfil del usuario en Mac OS X", e);
				throw new MozillaNotFoundException("No se ha podido obtener la carpeta con el perfil del usuario en Mac OS X", e);
			}
		}
		
		//-- Intento con el registro clasico de Mozilla
		regFile = new File(new File (System.getProperty("user.home")), "/.mozilla/appreg");
		if (regFile.exists()) {
			try {
				return getFirefoxProfileDirectory(regFile);
			} catch (Exception e) {
				logger.info("[MozillaKeyStoreManager]::No se ha podido obtener la carpeta con el perfil del usuario con el registro clásico de Mozilla", e);
				throw new MozillaNotFoundException("No se ha podido obtener la carpeta con el perfil del usuario con el registro clásico de Mozilla", e);
			}
		}

		logger.info("[MozillaKeyStoreManager]::No se ha podido obtener la carpeta con el perfil del usuario de Mozilla Firefox");
		throw new MozillaNotFoundException("No se ha podido obtener la carpeta con el perfil del usuario de Mozilla Firefox");
	}
	
    /**
     * Devuelve el directorio del perfil activo de Firefox. Si no hubiese perfil activo,
     * devolver&iacute;a el directorio del perfil por defecto y si tampoco lo hubiese
     * el del primer perfil encontrado. Si no hubiese perfiles configurados,
     * devolver&iacute;a {@code null}.
     * 
     * @param iniFile Fichero con la informaci&oacute;n de los perfiles de Firefox.
     * @return Directorio con la informaci&oacute;n del perfil.
     * @throws IOException Cuando ocurre un error abriendo o leyendo el fichero.
     */
    private File getFirefoxProfileDirectory(File iniFile) throws IOException {

    	String currentProfilePath = null;
    	
    	// Leemos el fichero con la informacion de los perfiles y buscamos el activo(el que esta bloqueado)
    	FirefoxProfile[] profiles = readProfiles(iniFile);
    	for (FirefoxProfile profile : profiles) {
    		if (isProfileLocked(profile)) {
    			currentProfilePath = profile.absolutePath;
    			logger.debug("[MozillaKeyStoreManager]::Perfil actual activo: " + currentProfilePath);
    			return new File (currentProfilePath);
    		}	
    	}

    	// Si no hay ninguno actualmente activo, tomamos el por defecto
    	if (currentProfilePath == null) {
    		for (FirefoxProfile profile : profiles) {
    			if (profile.isDefault) {
      			currentProfilePath = profile.absolutePath;
    			logger.debug("[MozillaKeyStoreManager]::Perfil actual por defecto: " + currentProfilePath);
    			return new File (currentProfilePath);
    			}
    		}
    	}

    	// Si no hay ninguno por defecto, se toma el primero
    	if (profiles.length > 0) {
    		currentProfilePath = profiles[0].absolutePath;
			logger.debug("[MozillaKeyStoreManager]::Primer perfil: " + currentProfilePath);
			return new File (currentProfilePath);
    	}

    	return null;
    }

    /**
     * Parsea la informacion de los perfiles declarada en el fichero "profiles.ini".
     * Para identificar correctamente los perfiles es necesario que haya al menos una
     * l&iacute;nea de separaci&oacute;n entre los bloques de informaci&oacute;n de
     * cada perfil.
     * @param iniFile Fichero con lainformaci&oacute;n de los perfiles.
     * @return Listado de perfiles completos encontrados.
     * @throws IOException Cuando se produce un error durante la lectura de la
     * configuraci&oacute;n.
     */
    private static FirefoxProfile[] readProfiles(File iniFile) throws IOException {
    	
    	final String NAME_ATR = "name=";
    	final String IS_RELATIVE_ATR = "isrelative=";
    	final String PATH_PROFILES_ATR = "path=";
    	final String IS_DEFAULT_ATR = "default=";
    	
    	String line = null;
    	Vector<FirefoxProfile> profiles = new Vector<FirefoxProfile>();
    	BufferedReader in = new BufferedReader(new FileReader(iniFile));
    	try {
    		while ((line = in.readLine()) != null) {
    			
    			// Buscamos un nuevo bloque de perfil
    			if (!line.trim().toLowerCase().startsWith("[profile"))
    				continue;
    			
    			FirefoxProfile profile = new FirefoxProfile();
    			while((line = in.readLine()) != null && line.trim().length() > 0 && !line.trim().toLowerCase().startsWith("[profile")) {
    				if(line.trim().toLowerCase().startsWith(NAME_ATR))
    					profile.name = line.trim().substring(NAME_ATR.length());
    				else if(line.trim().toLowerCase().startsWith(IS_RELATIVE_ATR))
    					profile.isRelative = line.trim().substring(IS_RELATIVE_ATR.length()).equals("1");
    				else if(line.trim().toLowerCase().startsWith(PATH_PROFILES_ATR))
    					profile.path = line.trim().substring(PATH_PROFILES_ATR.length());
    				else if(line.trim().toLowerCase().startsWith(IS_DEFAULT_ATR))
    					profile.isDefault = line.trim().substring(IS_DEFAULT_ATR.length()).equals("1");
    				else
    					break;
    			}
    			
    			// Debemos encontrar al menos el nombre y la ruta del perfil
    			if (profile.name != null || profile.path != null) {
    				profile.absolutePath =
    					profile.isRelative ? 
    						new File(iniFile.getParent(), profile.path).toString() :
    						profile.path;
    						
    				profiles.add(profile);
    			}
    		}
    	} catch (Exception e) {
    		throw new IOException("Ocurrio un error al leer la configuracion de los perfiles de Firefox: " + e);
    	} finally {
    		try { in.close(); } catch (Exception e) {}
    	}
    	
    	return profiles.toArray(new FirefoxProfile[profiles.size()]);
	}

    /**
     * Comprueba que un perfil de Firefox est&eacute; bloqueado. Un perfil esta
     * bloqueado cuando en su directorio se encuentra el fichero "parent.lock".
     * @param profile Informaci&oacute;n del perfil de Firefox.
     * @return Devuelve {@code true} si el perfil esta bloqueado, {@code false}
     * en caso contrario.
     */
    private static boolean isProfileLocked(FirefoxProfile profile) {
    	return 	new File(profile.absolutePath, "parent.lock").exists() ||	// En Windows
    					new File(profile.absolutePath, "lock").exists();					// En Linux
    }
    
    /**
     * Obtiene el nombre de la librería del provider NSS dependiendo del SO
     * 
     * @return Nombre de la librería del provider NSS
     */
    private String getMozillaNSSLibraryName () {
		String nssLib = "libsoftokn3.so";
		if(System.getProperty("os.name").contains("indows")) {
			nssLib = "softokn3.dll";
		}
		else if (System.getProperty("os.name").startsWith("Mac OS X")) {
			nssLib = "libsoftokn3.dylib";
		}

		return nssLib;
    }

	/**
	 * Carga las librerías de mozilla
	 * 
	 * @param mozillaFolder Carpeta donde se encuentran las librerías
	 */
	private void loadMozillaLibraries(File mozillaFolder) throws Throwable {
		
		logger.debug("[MozillaKeyStoreManager.loadMozillaLibraries]::Entrada::" + mozillaFolder);
		
		String[] librariesNames = null;
		
		if (!System.getProperty("os.name").contains("indows") && !System.getProperty("os.name").contains("inux") 
				&& !System.getProperty("os.name").contains("SunOS") && !System.getProperty("os.name").contains("olaris")) {
			throw new Exception ("Sistema Operativo desconocido");
		}
		
        // Fedora: librerías entre /usr/lib y /lib
        if (System.getProperty("os.name").contains("inux") && new File("/usr/lib/libsoftokn3.so").exists() 
        		&& new File("/lib/libnspr4.so").exists()) {
        	librariesNames = new String[] {
        		System.mapLibraryName("/usr/lib/libsoftokn3"),
        		System.mapLibraryName("/lib/libplds4"),
        		System.mapLibraryName("/usr/lib/libplds4"),
        		System.mapLibraryName("/lib/libplc4"), 
        		System.mapLibraryName("/usr/lib/libplc4"),
        		System.mapLibraryName("/lib/libnssutil3"),
        		System.mapLibraryName("/usr/lib/libnssutil3"),
        		System.mapLibraryName("/lib/libsqlite3"),
        		System.mapLibraryName("/usr/lib/libsqlite3"),
        		System.mapLibraryName("/lib/libmozsqlite3"),
        		System.mapLibraryName("/usr/lib/libmozsqlite3")
        	};
        }
        
		//-- Windows
		if (System.getProperty("os.name").contains("indows")) {
			librariesNames = new String[] {
					System.mapLibraryName("mozcrt19"),
					System.mapLibraryName("mozutils"),
					System.mapLibraryName("mozglue"),
					System.mapLibraryName("sqlite3"),
					System.mapLibraryName("mozsqlite3"),
					System.mapLibraryName("nspr4"),
					System.mapLibraryName("plds4"),
					System.mapLibraryName("plc4"),
					System.mapLibraryName("nssutil3"),
					System.mapLibraryName("nssdbm3"),
					System.mapLibraryName("freebl3")
				};
		} 
		
		//-- Linux/Unix
		if (System.getProperty("os.name").contains("inux") || System.getProperty("os.name").contains("SunOS") || 
				System.getProperty("os.name").contains("olaris")) {
			librariesNames = new String[] {
					"libnspr4.so",
					"libplds4.so",
					"libplc4.so",
					"libnssutil3.so",
					"libsqlite3.so",
					"libmozsqlite3.so"
				};
		} 
		
		for(String libraryName : librariesNames) {
			try {
				System.load(new File (mozillaFolder, libraryName).getAbsolutePath());
			} catch (UnsatisfiedLinkError e) {
				logger.debug("No se ha podido cargar la librería: " + new File (mozillaFolder, libraryName).getAbsolutePath(), e);
			}
		}
	}
	
	/**
	 * Obtiene las rutas completas hacia las bibliotecas (.dll o .so) de los m&oacute;dulos de seguridad externos (PKCS#11)
	 * instalados en Mozilla / Firefox, indexados por su descripci&oacute;n dentro de una
	 * <code>Hashtable</code>. 
	 * @return Nombres de las bibliotecas de los m&oacute;dulos de seguridad de Mozilla / Firefox
	 */
	private Hashtable<String, File> getMozillaPKCS11Modules() {
		try {
			final Hashtable<String, File> modsByDesc = new Hashtable<String, File>();
			for(MozillaPkcs11Module module : getCardModules()) {
				modsByDesc.put(module.getName(), module.getLibrary());
			}

			return modsByDesc; 
		}
		catch(Throwable t) {
			return new Hashtable<String, File>(0);
		}
	}

	/**
	 * Obtiene los m&oacute;dulos de seguridad PKCS#11 instalados en la base de datos <i>secmod.db</i>.
	 * @param profileDir Directorio del perfil del usuario activo de Mozilla / Firefox
	 * @return Vector con los m&oacute;dulos encontrados, el vector estar&aacute; vac&iacute;o
	 *         si se encuentra alg&uacute;n problema durante la b&acute;squeda
	 * @throws Exception 
	 * @throws AOException Cuando ocurre cualquier problema durante el proceso
	 */
	public ArrayList<MozillaPkcs11Module> getCardModules() throws Exception {
	    File secmod = new File(getSecModFolder(), "secmod.db");
	    if (!secmod.exists()) {
	        throw new Exception("El directorio del perfil de Mozilla proporcionado no contiene una base de datos de modulos (secmod.db)");
	    }
	    
	    ArrayList<MozillaPkcs11Module> lModules = new ArrayList<MozillaPkcs11Module>();
	    String contenidoSecmod = new String (Util.loadFile(secmod));
	    while(contenidoSecmod.indexOf(".dll") > -1) {
	    	int index = contenidoSecmod.indexOf(".dll") + 3;
	    	MozillaPkcs11Module module = getModule(contenidoSecmod, ".dll");
	    	if (!lModules.contains(module)) { lModules.add (module); }
	    	contenidoSecmod = contenidoSecmod.substring(0, index) + contenidoSecmod.substring (contenidoSecmod.indexOf(".dll") + 4);
	    }
	    while(contenidoSecmod.indexOf(".DLL") > -1) {
	    	int index = contenidoSecmod.indexOf(".DLL") + 3;
	    	MozillaPkcs11Module module = getModule(contenidoSecmod, ".DLL");
	    	if (!lModules.contains(module)) { lModules.add (module); }
	    	contenidoSecmod = contenidoSecmod.substring(0, index) + contenidoSecmod.substring (contenidoSecmod.indexOf(".DLL") + 4);
	    }
	    while(contenidoSecmod.indexOf(".so") > -1) {
	    	int index = contenidoSecmod.indexOf(".so") + 2;
	    	MozillaPkcs11Module module = getModule(contenidoSecmod, ".so");
	    	if (!lModules.contains(module)) { lModules.add (module); }
	    	contenidoSecmod = contenidoSecmod.substring(0, index) + contenidoSecmod.substring (contenidoSecmod.indexOf(".so") + 3);
	    }
	    while(contenidoSecmod.indexOf(".dylib") > -1) {
	    	int index = contenidoSecmod.indexOf(".dylib") + 5;
	    	MozillaPkcs11Module module = getModule(contenidoSecmod, ".dylib");
	    	if (!lModules.contains(module)) { lModules.add (module); }
	    	contenidoSecmod = contenidoSecmod.substring(0, index) + contenidoSecmod.substring (contenidoSecmod.indexOf(".dylib") + 5);
	    }
	    
		return lModules;
	}

	private MozillaPkcs11Module getModule(String contenidoSecmod, String extension) throws Exception {
	    while(contenidoSecmod.indexOf(extension) > -1) {
	    	int index = contenidoSecmod.indexOf(extension) + (extension.length() - 1);
	    	String name = "", module = "";
	    	boolean searchModule = true;
	    	while (true) {
	    		if (searchModule) {
	    			//-- Buscando el módulo
		    		module = contenidoSecmod.charAt(index) + module;
		    		if (contenidoSecmod.charAt(index) == ':' && (contenidoSecmod.charAt(index - 3) < 32 || contenidoSecmod.charAt(index - 3) > 126)) {
		    			module = contenidoSecmod.charAt(index - 1) + module;
		    			index = index - 3;
		    			searchModule = false;
		    		}
		    		if (contenidoSecmod.charAt(index) == '/' && (contenidoSecmod.charAt(index - 1) == ' ' ||
		    				contenidoSecmod.charAt(index - 1) < 32 || contenidoSecmod.charAt(index - 1) > 126)) {
		    			index = index - 2;
		    			searchModule = false;
		    		}
	    		} else {
	    			//-- Buscando el nombre
	    			if (contenidoSecmod.charAt(index) < 32 || contenidoSecmod.charAt(index) > 126) {
	    				break;
	    			}
	    			name = contenidoSecmod.charAt(index) + name;
	    		}
	    		index--;
	    	}
	    	return new MozillaPkcs11Module(getValidName (name), new File (module));
	    }
	    
		return null;
	}

	/*
	 * Obtiene un nombre válido para un provider: sólo letras y números
	 */
	private String getValidName(String name) {
		return name.replaceAll("[^a-zA-Z1-9]", "");
	}

    /** 
      * Copyright (C) 2011 [Gobierno de Espana]
      * Este método forma parte del "Cliente @Firma".
      * 
      * Busca la &uacute;ltima versi&oacute;n de firefox instalada en un sistema
      * Linux o Solaris
      * @return &Uacute;ltima versi&oacute;n instalada en el sistema */
     private static String searchLastFirefoxVersion(final String startDir) {
         final File directoryLib = new File(startDir);
         if (directoryLib.isDirectory()) {
             final String filenames[] = directoryLib.list();
             final List<String> firefoxDirectories = new ArrayList<String>();
             for (final String filename : filenames) {
                 if (filename.startsWith("firefox-")) { //$NON-NLS-1$
                     firefoxDirectories.add(filename.replace("firefox-", "")); //$NON-NLS-1$ //$NON-NLS-2$
                 }
             }
             if (firefoxDirectories.isEmpty()) {
                 return ""; //$NON-NLS-1$
             }
             for (int i = 0; i < firefoxDirectories.size(); i++) {
                 try {
                     Integer.getInteger(firefoxDirectories.get(i));
                 }
                 catch (final Exception e) {
                     firefoxDirectories.remove(i);
                 }
             }
             if (firefoxDirectories.size() == 1) {
                 return firefoxDirectories.get(0);
             }
             Collections.sort(firefoxDirectories, new Comparator<String>() {
                 /** {@inheritDoc} */
                 public int compare(final String o1, final String o2) {
                     return o1.compareTo(o2);
                 }
             });
             return firefoxDirectories.get(0);
         }
         return ""; //$NON-NLS-1$
     }
 
     /**
      * Copyright (C) 2011 [Gobierno de Espana]
      * Este método forma parte del "Cliente @Firma".
      */
     private void configureMacNSS(File mozillaFolder) throws Throwable {
 
         // Intentamos la carga, para ver si es necesaria la reconfiguracion
         try {
             System.load(new File (mozillaFolder, "libsoftokn3.dylib").getAbsolutePath()); //$NON-NLS-1$
             return; // Si funciona salimos sin hacer nada
         }
         catch (final Exception e) {
             // Se ignora el error
         }
         catch(final UnsatisfiedLinkError e) {
         	// Se ignora el error
         }
 
         final String[] libs = new String[] {
             "libmozutils.dylib", // Firefox 9 y superiores //$NON-NLS-1$
             "libnspr4.dylib", //$NON-NLS-1$
             "libplds4.dylib", //$NON-NLS-1$
             "libplc4.dylib", //$NON-NLS-1$
             "libmozsqlite3.dylib", //$NON-NLS-1$
             "libnssutil3.dylib" //$NON-NLS-1$
         };
 
         // Creamos enlaces simbolicos via AppleScript
         final StringBuilder sb = new StringBuilder();
         for (final String lib : libs) {
         	File fileLib = new File(mozillaFolder, lib);
             if (fileLib.exists()) {
                 sb.append("ln -s "); //$NON-NLS-1$
                 sb.append(fileLib.getAbsolutePath());
                 sb.append(" /usr/lib/"); //$NON-NLS-1$
                 sb.append(lib);
                 sb.append("; "); //$NON-NLS-1$
             }
         }
         try {
             final Class<?> scriptEngineManagerClass = Class.forName("javax.script.ScriptEngineManager"); //$NON-NLS-1$
             final Object scriptEngineManager = scriptEngineManagerClass.newInstance();
             final Method getEngineByNameMethod = scriptEngineManagerClass.getMethod("getEngineByName", String.class); //$NON-NLS-1$
             final Object scriptEngine = getEngineByNameMethod.invoke(scriptEngineManager, "AppleScript"); //$NON-NLS-1$
             final Class<?> scriptEngineClass = Class.forName("javax.script.ScriptEngine"); //$NON-NLS-1$
             final Method evalMethod = scriptEngineClass.getMethod("eval", String.class); //$NON-NLS-1$
             evalMethod.invoke(scriptEngine, "do shell script \"" + sb.toString() + "\" with administrator privileges"); //$NON-NLS-1$ //$NON-NLS-2$
 
             //new ScriptEngineManager().getEngineByName("AppleScript").eval("do shell script \"" + sb.toString() + "\" with administrator privileges");
         }
         catch(final Exception e) {
             logger.info("No se ha podido crear los enlaces simbolicos para NSS: " + e, e); //$NON-NLS-1$
         }
 
         // Y reintentamos la carga, para ver si ha surtido efecto
         try {
             System.load(new File (mozillaFolder, "libsoftokn3.dylib").getAbsolutePath()); //$NON-NLS-1$
         }
         catch (final Exception e) {
         	//-- A veces se carga dos veces en la misma JVM y la segunda pasa por estas excepciones
         	//-- porque ya ha cargado la librería, así que lo mejor es seguir
 //            throw new Exception("La configuracion de NSS para Mac OS X ha fallado por motivos de seguridad: " + e); //$NON-NLS-1$
         }
         catch(final UnsatisfiedLinkError e) {
 //        	throw new Exception("La configuracion de NSS para Mac OS X ha fallado: " + e); //$NON-NLS-1$
         }
     }
     
 	//-- Clases
 	
     /**
      * Clase que almacena la configuraci&oacute;n para la identificacion de un perfil
      * de Mozilla Firefox. 
      */
     private static class FirefoxProfile {
     	String name = null;
     	boolean isRelative = true;
     	String path = null;
     	String absolutePath = null;
     	boolean isDefault = false;
     }
     
     /**
      * Clase que representa la información de un módulo pkcs#11 definido en Mozilla
      */
     public class MozillaPkcs11Module {
     	private String name;
     	private File library;
 		public MozillaPkcs11Module(String name, File library) {
 			super();
 			this.name = name;
 			this.library = library;
 		}
 		public String getName() {
 			return name;
 		}
 		public void setName(String name) {
 			this.name = name;
 		}
 		public File getLibrary() {
 			return library;
 		}
 		public void setLibrary(File library) {
 			this.library = library;
 		}
     	
     }
 
     
     public static void main (String[] args) {
     	try {
 	    	MozillaKeyStoreManager manager = new MozillaKeyStoreManager();
 	    	ArrayList<MozillaPkcs11Module> modules = manager.getCardModules();
 	    	for (Iterator iterator = modules.iterator(); iterator.hasNext();) {
 	    		MozillaPkcs11Module module = (MozillaPkcs11Module) iterator.next();
 				System.out.println (module.getName() + " | " + module.getLibrary().getAbsolutePath());				
 			}
     	} catch (Exception e) {
     		e.printStackTrace();
     	}
     	
     }
 }